﻿/* 
 * Copyright (c) Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using ExtensionLoader;
using OpenMetaverse;
using CableBeachMessages;

namespace WorldServer.Extensions
{
    public class SparseMap<T>
    {
        protected Dictionary<string, T> hashtable = new Dictionary<string, T>();
        protected int lowerBounds0, upperBounds0;
        protected int lowerBounds1, upperBounds1;
        protected object syncRoot = new object();

        public int Count { get { return hashtable.Count; } }

        public SparseMap()
        {
        }

        public int GetLowerBound(int dimension)
        {
            if (dimension == 0)
                return lowerBounds0;
            else if (dimension == 1)
                return lowerBounds1;
            else
                throw new ArgumentOutOfRangeException("dimension");
        }

        public int GetUpperBound(int dimension)
        {
            if (dimension == 0)
                return upperBounds0;
            else if (dimension == 1)
                return upperBounds1;
            else
                throw new ArgumentOutOfRangeException("dimension");
        }

        public bool TryGetValue(int index0, int index1, out T value)
        {
            return hashtable.TryGetValue(IndexToHash(index0, index1), out value);
        }

        public bool TrySetValue(T value, int index0, int index1)
        {
            lock (syncRoot)
            {
                string key = IndexToHash(index0, index1);

                if (!hashtable.ContainsKey(key))
                {
                    hashtable[IndexToHash(index0, index1)] = value;

                    if (lowerBounds0 > index0) lowerBounds0 = index0;
                    if (upperBounds0 < index0) upperBounds0 = index0;

                    if (lowerBounds1 > index1) lowerBounds1 = index1;
                    if (upperBounds1 < index1) upperBounds1 = index1;

                    return true;
                }
            }

            return false;
        }

        public bool Contains(T value)
        {
            return hashtable.ContainsValue(value);
        }

        public void Clear()
        {
            lock (syncRoot)
            {
                hashtable.Clear();
                lowerBounds0 = lowerBounds1 = upperBounds0 = upperBounds1 = 0;
            }
        }

        public T this[int index0, int index1]
        {
            get { T value; if (TryGetValue(index0, index1, out value)) return value; else return default(T); }
            set { TrySetValue(value, index0, index1); }
        }

        protected string IndexToHash(int index0, int index1)
        {
            return index0 + "," + index1;
        }

        protected void HashToIndex(string hash, out int index0, out int index1)
        {
            int i = hash.IndexOf(',');
            index0 = Int32.Parse(hash.Substring(0, i));
            index1 = Int32.Parse(hash.Substring(i + 1));
        }
    }

    public class SimpleMap : IExtension<WorldServer>, IMapProvider
    {
        WorldServer server;
        SparseMap<RegionInfo> map = new SparseMap<RegionInfo>();
        DoubleDictionary<UUID, string, RegionInfo> regions = new DoubleDictionary<UUID, string, RegionInfo>();
        RegionInfo defaultRegion;
        object syncRoot = new object();

        public SimpleMap()
        {
        }

        public bool Start(WorldServer server)
        {
            this.server = server;
            return true;
        }

        public void Stop()
        {
        }

        public BackendResponse TryFetchRegion(UUID id, out RegionInfo region)
        {
            if (regions.TryGetValue(id, out region))
                return BackendResponse.Success;
            else
                return BackendResponse.NotFound;
        }

        public BackendResponse TryFetchRegion(uint x, uint y, out RegionInfo region)
        {
            if (map.TryGetValue((int)x, (int)y, out region))
                return BackendResponse.Success;
            else
                return BackendResponse.NotFound;
        }

        public BackendResponse TryFetchRegion(string name, out RegionInfo region)
        {
            if (regions.TryGetValue(name, out region))
                return BackendResponse.Success;
            else
                return BackendResponse.NotFound;
        }

        public BackendResponse TryFetchRegionNearest(uint x, uint y, out RegionInfo region)
        {
            RegionInfo foundRegion = new RegionInfo();
            float minDist = Single.MaxValue;

            lock (syncRoot)
            {
                regions.ForEach(
                    delegate(RegionInfo curRegion)
                    {
                        float x1 = x;
                        float x2 = curRegion.X;
                        float y1 = y;
                        float y2 = curRegion.Y;

                        float dist = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
                        if (dist < minDist)
                        {
                            minDist = dist;
                            foundRegion = curRegion;
                        }
                    }
                );
            }

            region = foundRegion;

            if (region.ID != UUID.Zero)
                return BackendResponse.Success;
            else
                return BackendResponse.NotFound;
        }

        public BackendResponse TryFetchDefaultRegion(out RegionInfo region)
        {
            region = defaultRegion;
            if (region.ID != UUID.Zero)
                return BackendResponse.Success;
            else
                return BackendResponse.NotFound;
        }

        public BackendResponse RegionSearch(string query, out List<RegionInfo> results)
        {
            query = query.ToLowerInvariant();
            List<RegionInfo> searchResults = new List<RegionInfo>();

            lock (syncRoot)
            {
                regions.ForEach(
                    delegate(KeyValuePair<string, RegionInfo> entry)
                    {
                        if (entry.Key.ToLowerInvariant().Contains(query))
                            searchResults.Add(entry.Value);
                    }
                );
            }

            results = searchResults;
            return BackendResponse.Success;
        }

        public int RegionCount()
        {
            return regions.Count;
        }

        public int OnlineRegionCount()
        {
            int online = 0;

            lock (syncRoot)
            {
                regions.ForEach(
                    delegate(RegionInfo region)
                    {
                        if (region.Online)
                            ++online;
                    }
                );
            }

            return online;
        }

        public int ForEach(Action<RegionInfo> action, int start, int count)
        {
            int pos = 0;
            int rowCount = 0;

            lock (syncRoot)
            {
                regions.ForEach(
                    delegate(RegionInfo region)
                    {
                        if (pos >= start && pos < start + count)
                        {
                            action(region);
                            ++rowCount;
                        }
                        ++pos;
                    }
                );
            }

            return rowCount;
        }
    }
}
